/* 
 * File:   MeepExecutor.cpp
 * Author: adriano
 * 
 * Created on February 8, 2013, 8:20 PM
 */


#include <cstdlib>
#include <iostream>
#include "MeepExecutor.h"
#include "Entities/ProjectSettings.h"

using namespace std;

MeepExecutor::MeepExecutor(int argc, 
                           char** argv, 
                           ProjectSettings* projSettings, 
                           SimulationSetup* setup, 
                           double epsilon(const meep::vec &)) 
{
    InitializeMPI(argc, argv);
    SetProjectSettings(projSettings);
    SetSetup(setup);
    SetGridVol();
    SetStrct(epsilon);
    SetSource();
    SetComponent();
    SetCurrentField();
    SetH5_file();
}

void MeepExecutor::InitializeMPI(int argc, char** argv)
{
    mpi = new meep::initialize(argc, argv);
}

void MeepExecutor::SetProjectSettings(ProjectSettings* projectSettings) 
{
    this->projectSettings = projectSettings;
}

ProjectSettings* MeepExecutor::GetProjectSettings() const 
{
    return projectSettings;
}

void MeepExecutor::SetSetup(SimulationSetup* setup) 
{
    this->setup = setup;
}

SimulationSetup* MeepExecutor::GetSetup() const 
{
    return setup;
}

void MeepExecutor::SetGridVol() 
{ 
    double xExtension;
    double yExtension;
    double zExtension;
        
    // set meep::grid_volume gridVol according with specifed dimension 
    switch (GetSetup()->GetDimension())
    {
        // one dimensional
        case 1:
            xExtension = GetSetup()->GetDomain()->GetXExtension() + 
                         GetSetup()->GetDomain()->GetPmlThickness();
            
            gridVol = meep::vol1d(xExtension, GetSetup()->GetDomain()->GetResolution());
            break;
        // bidimensional     
        case 2:
            xExtension = GetSetup()->GetDomain()->GetXExtension() + 
                         GetSetup()->GetDomain()->GetPmlThickness();
            
            yExtension = GetSetup()->GetDomain()->GetYExtension() + 
                         GetSetup()->GetDomain()->GetPmlThickness();
            
            gridVol = meep::vol2d(xExtension, yExtension, GetSetup()->GetDomain()->GetResolution());
            break;      
            // tridimensional     
        case 3:
            xExtension = GetSetup()->GetDomain()->GetXExtension() + 
                         GetSetup()->GetDomain()->GetPmlThickness();
                        
            yExtension = GetSetup()->GetDomain()->GetYExtension() + 
                         GetSetup()->GetDomain()->GetPmlThickness();
            
            zExtension = GetSetup()->GetDomain()->GetZExtension() + 
                         GetSetup()->GetDomain()->GetPmlThickness();
            
            gridVol = meep::vol3d(xExtension, yExtension, zExtension, GetSetup()->GetDomain()->GetResolution());
            break; 
    }   
    
    // center meep::grid_volume at origin
    gridVol.center_origin();
}

meep::grid_volume MeepExecutor::GetGridVol() const 
{
    return gridVol;
}

void MeepExecutor::SetStrct(double epsilon(const meep::vec &)) 
{
    std::string path = GetProjectSettings()->GetProjectDirectory() + "/post_processing";
    strct = new meep::structure(gridVol, epsilon, meep::pml(GetSetup()->GetDomain()->GetPmlThickness()));
    strct->set_output_directory(path.c_str());
}

meep::structure* MeepExecutor::GetStrct() const 
{
    return strct;
}

void MeepExecutor::SetSource() 
{
    if (GetSetup()->GetSource()->GetType() == "Contínua")
    {
        source = new meep::continuous_src_time(GetSetup()->GetSource()->GetFrequency(), 
                                               GetSetup()->GetSource()->GetWidth(), 
                                               GetSetup()->GetSource()->GetStartTime(),
                                               GetSetup()->GetSource()->GetEndTime());
        
        //cout << "last source time A: " << src.last_time() << endl;
        //this->source = dynamic_cast<meep::src_time*>(&src); 
        
//        if (this->source != NULL)
//            cout << "source last time: " << this->source->last_time()  << endl;
    }
    else // Gaussiana
    {
        source = new meep::gaussian_src_time(GetSetup()->GetSource()->GetFrequency(), 
                                             GetSetup()->GetSource()->GetWidth());
    }
    
    
}

meep::src_time* MeepExecutor::GetSource() const 
{
    return source;
}

void MeepExecutor::SetComponent() 
{
    if (GetSetup()->GetField()->GetFieldType() == "Elétrico")
    {
        if (GetSetup()->GetField()->GetDirection() == "x")
        {
            component = meep::Ex;
        }
        else if (GetSetup()->GetField()->GetDirection() == "y")
        {
            component = meep::Ey;
        }
        else
        {
            component = meep::Ez;
        }
    }
    else
    {
        if (GetSetup()->GetField()->GetDirection() == "x")
        {
            component = meep::Hx;
        }
        else if (GetSetup()->GetField()->GetDirection() == "y")
        {
            component = meep::Hy;
        }
        else
        {
            component = meep::Hz;
        }
    }
}

meep::component MeepExecutor::GetComponent() const 
{
    return component;
}

void MeepExecutor::SetCurrentField() 
{       
    std::string path = GetProjectSettings()->GetProjectDirectory() + "/post_processing";    
    currentField = new meep::fields(GetStrct());    
    currentField->set_output_directory(path.c_str());
    currentField->use_real_fields();    
    currentField->output_hdf5(meep::Dielectric, GetGridVol().surroundings());
    
    if (GetSetup()->GetDimension() == 3)
    {
        currentField->add_point_source(GetComponent(), (*GetSource()), meep::vec(GetSetup()->GetSource()->GetPosition()[0], 
                                                                                 GetSetup()->GetSource()->GetPosition()[1],
                                                                                 GetSetup()->GetSource()->GetPosition()[2]));
    }
    else
    {
        currentField->add_point_source(GetComponent(), (*GetSource()), meep::vec(GetSetup()->GetSource()->GetPosition()[0], 
                                                                                 GetSetup()->GetSource()->GetPosition()[1]));      

    }
}

meep::fields* MeepExecutor::GetCurrentField() const 
{
    return currentField;
}

void MeepExecutor::SetH5_file() 
{      
    h5_file = currentField->open_h5file(ComponentToString().c_str(), meep::h5file::WRITE, "", true);
}

meep::h5file* MeepExecutor::GetH5_file() const 
{
    return h5_file;
}

std::string MeepExecutor::ComponentToString() const
{
    std::string comp;
    
    switch (GetComponent())
    {
        case meep::Ex:
            comp = "ex";
            break;
        case meep::Ey:
            comp = "ey";
            break;
        case meep::Ez:
            comp = "ez";
            break;
        case meep::Hx:
            comp = "hx";
            break;
        case meep::Hy:
            comp = "hy";
            break;
        case meep::Hz:
            comp = "hz";
            break;
        default:
            comp = "ex";
    }
    
    return comp;
}

void MeepExecutor::Run()
{    
    cout << "current Field time : " << GetCurrentField()->time() << endl;
    cout << "last Field time :    " << GetCurrentField()->last_source_time() << endl;    
    
    if (GetProjectSettings()->IsMovie2D() || GetProjectSettings()->IsMovie3D())
    {        
        while (GetCurrentField()->time() < GetCurrentField()->last_source_time())
        {   
            GetCurrentField()->output_hdf5(GetComponent(), GetGridVol().surroundings(), this->h5_file, true, false, "");
            GetCurrentField()->step();        
        }
    }
    else
    {
        while (GetCurrentField()->time() < GetCurrentField()->last_source_time())
        {           
            GetCurrentField()->step();        
        }
        
        GetCurrentField()->output_hdf5(GetComponent(), GetGridVol().surroundings(), GetH5_file(), true, false, "");
    }
    
    SetElapsed_time(mpi->elapsed_time());
}

void MeepExecutor::GetFieldInDetectors()
{
//    using namespace meep;
//    
//    const char *dirname = make_output_directory(__FILE__);
//    GetStrct().set_output_directory(dirname);
//
//    complex<double>  *out;
//    monitor_point *r  = NULL;
//    monitor_point *r2  = NULL;
//
//    FILE *myout = create_output_file(dirname, GetC);
//
//   while (field.time() <= field.last_source_time() + 400 && !interrupt)
//   {
//        // Now we will start taking data!
//        field.step();
//        r  = field.get_new_point(vec(setup->GetField()->GetInitialPosition()[0], setup->GetField()->GetInitialPosition()[1], setup->GetField()->GetInitialPosition()[2]), r);
//        r2 = field.get_new_point(vec(setup->GetField()->GetFinalPosition()[0], setup->GetField()->GetFinalPosition()[1], setup->GetField()->GetFinalPosition()[2]), r2);
//
//        master_fprintf(myout, "%g\t%g\t%g\t%g\t\%g\t%g\t%g\t%g\n", field.time(),
//                real(r->get_component(Ex)), imag(r->get_component(Ex)),
//                real(r->get_component(Ex)) * real(r->get_component(Ex)) + imag(r->get_component(Ex))*imag(r->get_component(Ex)),
//                real(r2->get_component(Ex)),imag(r2->get_component(Ex)),
//                real(r2->get_component(Ex))*real(r2->get_component(Ex)) + imag(r2->get_component(Ex))*imag(r2->get_component(Ex)), 
//                10 * log10( (real(r->get_component(Ex)) * real(r->get_component(Ex)) + imag(r->get_component(Ex)) * 
//                imag(r->get_component(Ex)) ) / ( real(r2->get_component(Ex)) * real(r2->get_component(Ex)) + imag(r2->get_component(Ex)) * 
//                imag(r2->get_component(Ex))))   
//        );
//   }
}

void MeepExecutor::SetElapsed_time(double elapsed_time) 
{
    this->elapsed_time = elapsed_time;
}

double MeepExecutor::GetElapsed_time() const 
{
    return elapsed_time;
}

MeepExecutor::~MeepExecutor() 
{
    mpi->~initialize();
}
